/*
j8583 A Java implementation of the ISO8583 protocol
Copyright (C) 2007 Enrique Zamudio Lopez

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
package com.bcinfo.iccard;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.text.ParseException;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;

import com.bcinfo.iccard.codec.CodeUtils;
import com.bcinfo.iccard.msg.ISOMessageFactory;
import com.bcinfo.iccard.util.DataCryptor;
import com.bcinfo.wapportal.util.Encryption;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.parse.ConfigParser;

/**
 * Implements a very simple TCP client application that connects to a server and
 * sends some requests, displaying the response codes and confirmations.
 * 
 * @author Enrique Zamudio
 */
public class Client implements Runnable {

	private static final Logger log = Logger.getLogger(Client.class);

	private static MessageFactory<IsoMessage> mfact;
	private static ConcurrentHashMap<String, IsoMessage> pending = new ConcurrentHashMap<String, IsoMessage>();

	private Socket sock;
	private boolean done = false;

	public Client(Socket socket) {
		sock = socket;
	}

	public void run() {
		byte[] lenbuf = new byte[2];
		try {
			// For high volume apps you will be better off only reading the
			// stream in one thread
			// and then using another thread to parse the buffers and process
			// the responses
			// Otherwise the network buffer might fill up and you can miss a
			// message.
			while (sock != null && sock.isConnected()) {
				sock.getInputStream().read(lenbuf);
				int size = ((lenbuf[0] & 0xff) << 8) | (lenbuf[1] & 0xff);
				byte[] tpduBuf=new byte[5];
	            //读TPDU头
				sock.getInputStream().read(tpduBuf);
				
				byte[] buf = new byte[size-5];
				
				// We're not expecting ETX in this case
				if (sock.getInputStream().read(buf) == size) {
					try {
						// We'll use this header length as a reference.
						// In practice, ISO headers for any message type are the
						// same length.
						String respHeader = mfact.getIsoHeader(0x200);
						IsoMessage resp = mfact.parseMessage(buf,
								respHeader == null ? 12 : respHeader.length());
						log.debug(String.format("Read response %s conf %s: %s",
								resp.getField(11), resp.getField(38),
								new String(buf)));
						pending.remove(resp.getField(11).toString());
						ISOMessageFactory.print(resp);
					} catch (ParseException ex) {
						log.error("Parsing response", ex);
					}
				} else {
					pending.clear();
					return;
				}
			}
		} catch (IOException ex) {
			if (done) {
				log.info(String.format(
						"Socket closed because we're done (%d pending)",
						pending.size()));
			} else {
				log.error(String.format("Reading responses, %d pending",
						pending.size()), ex);
				try {
					sock.close();
				} catch (IOException ex2) {
				}
				;
			}
		} finally {
			if (sock != null) {
				try {
					sock.close();
				} catch (IOException ex) {
				}
				;
			}
		}
	}

	protected void stop() {
		done = true;
		try {
			sock.close();
		} catch (IOException ex) {
			log.error("Couldn't close socket");
		}
		sock = null;
	}

	public static void main(String[] args) throws Exception {
		log.debug("Reading config");
		mfact = ConfigParser
				.createFromClasspathConfig("j8583.xml");
		mfact.setAssignDate(true);
		//mfact.setTraceNumberGenerator(new SimpleTraceGenerator((int) (System.currentTimeMillis() % 10000)));
		System.err.println("Connecting to server");
		Socket sock = new Socket("localhost", 11100);
		// Send 10 messages, then wait for the responses
		Client client = new Client(sock);
		Thread reader = new Thread(client, "j8583-client");
		reader.start();
		
		IsoMessage req = assembleMsg(0x0500);

		pending.put(req.getField(11).toString(), req);
		System.err
				.println(String.format("Sending request %s", req.getField(11)));
		byte[] tpdu=new byte[]{96,0,22,0,0};
		byte[] data=req.writeData();
		int len=data.length+5;
		byte[] lenBuf=Arrays.copyOfRange(CodeUtils.intToByte(len), 2, 4);
		OutputStream out=sock.getOutputStream();
		out.write(lenBuf);
		out.write(tpdu);
		out.write(data);
		out.flush();
		log.debug("Waiting for responses");
		while (pending.size() > 0 && sock.isConnected()) {
			Thread.sleep(500);
		}
		client.stop();
		reader.interrupt();
		log.debug("DONE.");
	}

    /**
     * 一句话描述 <br/>
     * <p>Description: TODO(详细描述) 
     * <br/>
     * <p>Author: wangguangchun<br/>
     * <p>Date: 2013-12-5-上午11:12:25<br/>
     * <p>
     * @return   
     * 
     */
    private static IsoMessage assembleMsg(int msgType)
    {
        IsoMessage req=new IsoMessage();
        switch( msgType) {
            case 0x0800:{
                req.setIsoHeader("1000");
                req.setType(0x0800);
                req.setValue(3, "990000", IsoType.NUMERIC, 6);
                req.setValue(11, 112244, IsoType.NUMERIC, 6);
                req.setValue(12, 115430, IsoType.NUMERIC, 6);
                req.setValue(13, 20131129, IsoType.NUMERIC, 8);
                req.setValue(39, "A0", IsoType.ALPHA, 2);
                req.setValue(41, "20128106", IsoType.ALPHA, 8);
                req.setValue(42, "000000000000119", IsoType.ALPHA, 15);
                ISOMessageFactory.print(req);
                break;
            }
            case 0x0100:{
                byte[] pin=DataCryptor.getPinBlock("123456","000000051591285");
                byte[] pinData=DataCryptor.encryption(pin, "9acce3927e601011".toUpperCase());
                req.setIsoHeader("1000");
                req.setType(0x0100);
                req.setValue(2, "000000051591285", IsoType.LLVAR,0);
                req.setValue(3, "300000", IsoType.NUMERIC, 6);
                req.setValue(11, 112244, IsoType.NUMERIC, 6);
                req.setValue(22, 1, IsoType.NUMERIC,3);
                req.setValue(24, 3, IsoType.NUMERIC,3);
                req.setValue(25, 1, IsoType.NUMERIC,2);
                req.setValue(35, "磁道2的数据", IsoType.LLVAR,0);
                req.setValue(41, "20128106", IsoType.ALPHA, 8);
                req.setValue(42, "000000000000119", IsoType.ALPHA, 15);
                req.setValue(52, pinData, IsoType.BINARY, 8);
                byte[] empty=new byte[8];
                System.out.println("before set 64:"+req.writeData().length);
                //req.setValue(64, empty, IsoType.BINARY, 8);
                IsoValue<byte[]> emptyMac=new IsoValue<byte[]>(IsoType.BINARY, empty,8);
                req.setField(64, emptyMac);
                System.out.println("after set 64:"+req.writeData().length);
                byte[] data=req.writeData();
                byte[] toMacBuf = Arrays.copyOfRange(data, 4, data.length -16);
                DataCryptor.printByteArray(toMacBuf);
                byte[] macBuf=DataCryptor.XOR_DES_MAC_BINARY("b828b3cccefc6e7f".toUpperCase(),DataCryptor.getHexStr(toMacBuf) , 0);
                //req.setValue(64, macBuf, IsoType.BINARY, 8);
                IsoValue<byte[]> mac=new IsoValue<byte[]>(IsoType.BINARY, macBuf,8);
                req.setField(64, mac);
                ISOMessageFactory.print(req);
                break;
            }
            case 0x0200:{
                byte[] pin=DataCryptor.getPinBlock("123456","000000051591285");
                byte[] pinData=DataCryptor.encryption(pin, "9acce3927e601011".toUpperCase());
                req.setIsoHeader("1000");
                req.setType(0x0200);
                req.setValue(2, "000000051591285", IsoType.LLVAR,0);
                req.setValue(3, "000000", IsoType.NUMERIC, 6);
                req.setValue(4, 620, IsoType.NUMERIC,12);
                req.setValue(11, 112244, IsoType.NUMERIC, 6);
                req.setValue(22, 21, IsoType.NUMERIC,3);
                req.setValue(24, 3, IsoType.NUMERIC,3);
                req.setValue(25, 1, IsoType.NUMERIC,2);
                req.setValue(35, "000000051591285", IsoType.LLVAR,0);
                req.setValue(41, "20128106", IsoType.ALPHA, 8);
                req.setValue(42, "000000000000119", IsoType.ALPHA, 15);
                req.setValue(52, pinData, IsoType.BINARY, 8);
                req.setValue(62, "000000000037", IsoType.NUMERIC, 12);
                byte[] empty=new byte[8];
                IsoValue<byte[]> emptyMac=new IsoValue<byte[]>(IsoType.BINARY, empty,8);
                req.setField(64, emptyMac);
                byte[] data=req.writeData();
                //j8583包处理binary的域使用了16字节存放8字节的内容，这里需要后期解决修改
                byte[] toMacBuf = Arrays.copyOfRange(data, 4, data.length -16);
                DataCryptor.printByteArray(toMacBuf);
                byte[] macBuf=DataCryptor.XOR_DES_MAC_BINARY("b828b3cccefc6e7f".toUpperCase(),DataCryptor.getHexStr(toMacBuf) , 0);
                //req.setValue(64, macBuf, IsoType.BINARY, 8);
                IsoValue<byte[]> mac=new IsoValue<byte[]>(IsoType.BINARY, macBuf,8);
                req.setField(64, mac);
                ISOMessageFactory.print(req);
                break;
            }
            case 0x0201:{
                byte[] pin=DataCryptor.getPinBlock("123456","000000051591285");
                byte[] pinData=DataCryptor.encryption(pin, "9acce3927e601011".toUpperCase());
                req.setIsoHeader("1000");
                req.setType(0x0200);
                req.setValue(2, "000000051591285", IsoType.LLVAR,0);
                req.setValue(3, "000000", IsoType.NUMERIC, 6);
                req.setValue(4, 620, IsoType.NUMERIC,12);
                req.setValue(11, 112244, IsoType.NUMERIC, 6);
                req.setValue(22, 31, IsoType.NUMERIC,3);
                req.setValue(24, 3, IsoType.NUMERIC,3);
                req.setValue(25, 1, IsoType.NUMERIC,2);
                req.setValue(35, "000000051591285", IsoType.LLVAR,0);
                req.setValue(41, "20128106", IsoType.ALPHA, 8);
                req.setValue(42, "000000000000119", IsoType.ALPHA, 15);
                try
                {
                    String encrypt=Encryption.encrypt(new StringBuffer("594761386748876999")).toString();
                    req.setValue(48, encrypt, IsoType.LLLVAR,0);
                }
                catch (Exception e)
                {
                }
                req.setValue(52, pinData, IsoType.BINARY, 8);
                req.setValue(62, "111111111114", IsoType.NUMERIC, 12);
                byte[] empty=new byte[8];
                IsoValue<byte[]> emptyMac=new IsoValue<byte[]>(IsoType.BINARY, empty,8);
                req.setField(64, emptyMac);
                byte[] data=req.writeData();
                //j8583包处理binary的域使用了16字节存放8字节的内容，这里需要后期解决修改
                byte[] toMacBuf = Arrays.copyOfRange(data, 4, data.length -16);
                DataCryptor.printByteArray(toMacBuf);
                byte[] macBuf=DataCryptor.XOR_DES_MAC_BINARY("b828b3cccefc6e7f".toUpperCase(),DataCryptor.getHexStr(toMacBuf) , 0);
                //req.setValue(64, macBuf, IsoType.BINARY, 8);
                IsoValue<byte[]> mac=new IsoValue<byte[]>(IsoType.BINARY, macBuf,8);
                req.setField(64, mac);
                ISOMessageFactory.print(req);
                break;
            }
            case 0x0500:{
                byte[] pin=DataCryptor.getPinBlock("123456","000000051591285");
                byte[] pinData=DataCryptor.encryption(pin, "9acce3927e601011".toUpperCase());
                req.setIsoHeader("1000");
                req.setType(0x0500);
                req.setValue(2, "000000051591285", IsoType.LLVAR,0);
                req.setValue(3, "000003", IsoType.NUMERIC, 6);
                req.setValue(4, 120000, IsoType.NUMERIC,12);
                req.setValue(11, 112244, IsoType.NUMERIC, 6);
                req.setValue(22, 21, IsoType.NUMERIC,3);
                req.setValue(24, 3, IsoType.NUMERIC,3);
                req.setValue(25, 1, IsoType.NUMERIC,2);
                req.setValue(35, "000000051591285", IsoType.LLVAR,0);
                req.setValue(37, "000000000039", IsoType.ALPHA,12);
                req.setValue(41, "20128106", IsoType.ALPHA, 8);
                req.setValue(42, "000000000000119", IsoType.ALPHA, 15);
                req.setValue(52, pinData, IsoType.BINARY, 8);
                byte[] empty=new byte[8];
                System.out.println("before set 64:"+req.writeData().length);
                IsoValue<byte[]> emptyMac=new IsoValue<byte[]>(IsoType.BINARY, empty,8);
                req.setField(64, emptyMac);
                System.out.println("after set 64:"+req.writeData().length);
                byte[] data=req.writeData();
                //j8583包处理binary的域使用了16字节存放8字节的内容，这里需要后期解决修改
                byte[] toMacBuf = Arrays.copyOfRange(data, 4, data.length -16);
                DataCryptor.printByteArray(toMacBuf);
                byte[] macBuf=DataCryptor.XOR_DES_MAC_BINARY("b828b3cccefc6e7f".toUpperCase(),DataCryptor.getHexStr(toMacBuf) , 0);
                //req.setValue(64, macBuf, IsoType.BINARY, 8);
                IsoValue<byte[]> mac=new IsoValue<byte[]>(IsoType.BINARY, macBuf,8);
                req.setField(64, mac);
                ISOMessageFactory.print(req);
                break;
            }
        }
        return req;
    }
   
}
